#!/usr/bin/env ruby
require "sequel"
require 'gpgme'
require 'io/console'
require "clipboard"
require "passgen"

class Taina
  def initialize()
    #Crypting Gem with ASCII armored format enabled
    @crypto = GPGME::Crypto.new  :armor => true
    #hold the Connection to Db with sequel Orm
    @db = create_db()
    #create database table if the exist nothing will happen
    create_table()
    #create dataset for table names
    @dataset = @db[:Names]
  end

  def create_db()
    db_name = ENV['TAINA_DB']
    #Get Home Directory for current user
    home = ENV['HOME']
    if db_name == nil      
      db = Sequel.sqlite(home+'/taina.db')      
    else
      db = Sequel.sqlite(db_name)      
    end
    return db
  end
  #This Method Used to Create Table in sqlite Database
  def create_table()
    #suppressing error, that because if the database table are already created then the following line will rise an expection
    #so we are supprising it
    begin
      #Create Table Names
      @db.create_table :Names do
      primary_key :id
      String :name, unique: true, null: false
      String :password, unique: false, null: false
    end
      #empty rescue block , we don't need to do anything if the table creation failed
      rescue
    end
  end
  #This Method Used to Set The Default Gpg Key Id
  def default_key()
    #check if there is no enough argument passed in the command line then terminate the script

    leng = ARGV.length
    if leng<2
      #inform user then quit the application
      STDERR.puts("""\"taina default-key\" requires exactly 1 argument.
        
      Usage:  taina default-key gpg-id

set gpg-id as default key
""")
      exit()
    end
    #Get The GPG key ID from Environment Variables   
    key = ENV['GPG-KEY']
    #terminate the script if it couldn't find the encryption key
    ensure_key_existion(key)
    #Get Home Directory for current user
    home = ENV['HOME']
    #Rite The GPG ID to file in Home Directory for current user
    File.open(home+"/.taina-id", 'w') { |file| file.write(ARGV[1]) }
  end

  #This Method Used to Read gpg key from file in home user
  def read_key()
    #Get Home Directory for current user
    home = ENV['HOME']
    #Get The GPG key ID from Environment Variables   
    gpg_env = ENV['GPG_KEY']
    if gpg_env != nil
      return gpg_env
    end
    begin
      #Read GPG Key Id From File
      id = File.open(home+'/.taina-id', &:readline)
      rescue
      STDERR.puts("""No GPG Key:
 use taina default-key to set New Default gpg key
or set THE key Useing GPG_key environment Variable""")
      exit()
    end

return id
end

  #This Method Used to Save New Password to The Database
  def add()
    #check if there is no enough argument passed in the command line
    leng = ARGV.length
    if leng<2
      #inform user then quit the application
      STDERR.puts("""\"taina add\" requires exactly 1 argument.
        
      Usage:  taina add name

    save password for website or account
      """)
    exit()
  end
  key = read_key()

  #terminate the script if it couldn't find the encryption key
  ensure_key_existion(key)

    #ask user to enter password
    password = IO::console.getpass "Enter Password: "
    #encrypt the password with the key
    text = @crypto.encrypt password, :recipients => key
    begin
      #save the password
      @dataset.insert(name: ARGV[1], password: text.to_s)
      #if the name is already exist it will rise an  error cause name field is unique 
      rescue
        STDERR.puts("This name already exists")
    end
  end
  #This Method Used to Update Password in Database
  def update()
    #check if there is no enough argument passed in the command line then terminate the script
    leng = ARGV.length
    if leng<2
      STDERR.puts("""\"taina update\" requires exactly 1 argument.

      Usage:  taina update name

      Update password for website or account
      """)
      exit()
    end
    key = read_key()

    #terminate the script if it couldn't find the encryption key
    ensure_key_existion(key)
    begin
      #get the password field from database that associated with the passed name
      name = @dataset.where(name: ARGV[1])
      #if there is none a exception will a rise and we will inform user and terminate script
      rescue
        STDERR.puts("Cant Find This Name")
        exit()
    end
    #ask for password from user
    password = IO::console.getpass "Enter Password: "
    #encrypt password 
    text = @crypto.encrypt password, :recipients => key
    #update the password associated with the passed name in the database
    name.update(:password => text.to_s)
  end
  #print and Copy Password to clipboard
  def show()
    #check if there is no enough argument passed in the command line then terminate the script
    leng = ARGV.length
    if leng<2
      STDERR.puts("""\"taina show\" requires exactly 1 argument.

      Usage:  taina show name

      show password for website or account
      """)
      exit()
    end
    #get the field that associated with the passed name
    name = @dataset.where(name: ARGV[1]).first
    begin
      #decrypt the password that stored in field associated with the passed name 
      text = @crypto.decrypt name[:password]
#, :recipients => key
      #print the password
      puts text.to_s
      #copy password to clip board
      Clipboard.copy text.to_s
      #inform the user about copying password to clipboard
      puts("The Password Copyd to Clipboard")
      #if there is no field that associated with the passed name inform the user
      rescue
        STDERR.puts("Cant Find This Name")
    end
  end
  #delete the password from the database
  def delete()
    #check if there is no enough argument passed in the command line then terminate the script
    leng = ARGV.length
    if leng ==1
      STDERR.puts("""\"taina delete\" requires exactly 1 argument.

      Usage:  taina delete name

      delete password for website or account
      """)
      exit()
    end
    #delete field that assoicated with the passed name
    name = @dataset.where(name: ARGV[1]).delete
    exit()
  end
  #show all the keys and passwords stored in the database
  def show_all()
      @dataset.each do |key|
        puts key[:name]
      end
  end
  def analyze_password()
    #ask for password from user
    password = IO::console.getpass "Enter Password: "    
    info = Passgen::analyze(password)
    puts("score: "+info.score.to_s)
    puts("complexity: "+info.complexity.to_s)
  end
  def generate_password()
    #check if there is no enough argument passed in the command line
    leng = ARGV.length
    if leng<2
      #inform user then quit the application
      STDERR.puts("""\"taina generate-password\" requires exactly 1 argument.
        
      Usage:  taina generate-password length

      generate password for website or account
      """)
    exit()
    end
    password = Passgen::generate(:length => ARGV[1].to_i, :symbols => true)
    puts(password)
    #copy password to clip board
    Clipboard.copy password
    #inform the user about copying password to clipboard
    puts("The Password Copyd to Clipboard")
  end
  #check if key and if not terminate Script
  def ensure_key_existion(key)
    find = GPGME::Key.find(:secret, key)
    if find.length==0
      STDERR.puts("Cant Find the key")
      exit()
    end
  end

  #show avaliable commands
  def help()
    STDERR.puts("""
    Usage:  taina COMMAND
    Commands:
    add Create password to website or account
    update Update The Password for website or account
    delete Delete password to website or account
    show view password to website or account
    show-all List all websites and accounts in database
    default-key set gpg-id as default key
    generate-password Generate Password for website or account
    analyze-password Find Strength of Password
    """)
  end

  #first method to run , it will recive the first argument from command line and decide which function will run depending on it
  def run()
    if ARGV[0]=="add"
      add()
    elsif ARGV[0]=="update"
      update()
          elsif ARGV[0]=="show"
      show() 
    elsif ARGV[0]=="show-all"
      show_all()
    elsif ARGV[0]=="delete"
      delete()
    elsif ARGV[0] == "generate-password"
      generate_password()
    elsif ARGV[0] == "analyze-password"
      analyze_password()

    elsif ARGV[0]=="default-key"
      default_key()      
    else
      help()
    end
  end
end
#create instance from the main class
app = Taina.new()
app.run()
